Micron Document
🎖️GitЯра🎖️

Node / meshtastic / Meshtastic-Android / files / scripts / play-track-preflight.sh

Displaying Raw • Download

scripts/play-track-preflight.sh claude/stable-debug-keystore (df1f846f) Text, 3.82 KB

T8b949e#!/usr/bin/env bash
T8b949e# Preflight for promote.yml: check whether VERSION_CODE is already live on the
T8b949e# target Play track, so a re-dispatched promotion can no-op instead of creating
T8b949e# a redundant Play submission (each submission cancels and restarts any review
T8b949e# already in flight — see the Jul 2026 v2.8.0 submission-churn incident).
T8b949e#
T8b949e# Usage: play-track-preflight.sh <service-account.json> <package> <track> <version_code>
T8b949e# Writes already_on_track=true|false to $GITHUB_OUTPUT (or stdout when unset).
T8b949e#
T8b949e# Fail-open by design: on any API/auth error it reports already_on_track=false
T8b949e# and exits 0, so a preflight hiccup never blocks a legitimate promotion —
T8b949e# `fastlane supply` uses the same credentials and will surface real failures.

Tffa657set -u

Te6edf3KEYTff7b72=Ta5d6ff"Tffd700${Te6edf31Tb4b4b4:Tb4b4b4?usageTb4b4b4:Tb4b4b4 play-track-preflight.sh <service-account.json> <package> <track> <version_code>Tffd700}Ta5d6ff"
Te6edf3PKGTff7b72=Ta5d6ff"Tffd700${Te6edf32Tb4b4b4:Tb4b4b4?missing packageTffd700}Ta5d6ff"
Te6edf3TRACKTff7b72=Ta5d6ff"Tffd700${Te6edf33Tb4b4b4:Tb4b4b4?missing trackTffd700}Ta5d6ff"
Te6edf3VERSION_CODETff7b72=Ta5d6ff"Tffd700${Te6edf34Tb4b4b4:Tb4b4b4?missing version_codeTffd700}Ta5d6ff"
Te6edf3SCOPETff7b72=Ta5d6ff"https://www.googleapis.com/auth/androidpublisher"
Te6edf3OUTTff7b72=Ta5d6ff"Tffd700${Te6edf3GITHUB_OUTPUTTff7b72:-Tb4b4b4/dev/stdoutTffd700}Ta5d6ff"

emitTff7b72(Tff7b72) Tff7b72{
Tffa657echo Ta5d6ff"Ta5d6ffalready_on_track=Te6edf3$1Ta5d6ff" >> Ta5d6ff"Te6edf3$OUTTa5d6ff"
Tffa657exit T79c0ff0
Tff7b72}

fail_openTff7b72(Tff7b72) Tff7b72{
Tffa657echo Ta5d6ff"Ta5d6ff::warning::Play preflight failed (Te6edf3$1Ta5d6ff) — proceeding with promotion (fail-open).Ta5d6ff"
emit Tffa657false
Tff7b72}

Te6edf3TMPTff7b72=Ta5d6ff"Tff7b72$(mktemp -dTff7b72)Ta5d6ff" Tff7b72|| fail_open Ta5d6ff"mktemp"
Tffa657trap Ta5d6ff'rm -rf "$TMP"' EXIT

b64urlTff7b72(Tff7b72) Tff7b72{ openssl base64 -A Tb4b4b4| tr Ta5d6ff'+/' Ta5d6ff'-_' Tb4b4b4| tr -d Ta5d6ff'='Tb4b4b4; Tff7b72}

Te6edf3CLIENT_EMAILTff7b72=Tff7b72$(python3 -c Ta5d6ff'import json,sys;print(json.load(open(sys.argv[1]))["client_email"])' Ta5d6ff"Te6edf3$KEYTa5d6ff" T79c0ff2>/dev/nullTff7b72) Tffea00\
Tff7b72|| fail_open Ta5d6ff"unreadable service-account key"
python3 -c Ta5d6ff'import json,sys;print(json.load(open(sys.argv[1]))["private_key"])' Ta5d6ff"Te6edf3$KEYTa5d6ff" > Ta5d6ff"Te6edf3$TMPTa5d6ff/key.pemTa5d6ff" T79c0ff2>/dev/null Tffea00\
Tff7b72|| fail_open Ta5d6ff"key missing private_key"

Te6edf3NOWTff7b72=Tff7b72$(date +%sTff7b72)
Te6edf3HEADERTff7b72=Tff7b72$(Tffa657printf Ta5d6ff'{"alg":"RS256","typ":"JWT"}' Tb4b4b4| b64urlTff7b72)
Te6edf3CLAIMTff7b72=Tff7b72$(Tffa657printf Ta5d6ff'{"iss":"%s","scope":"%s","aud":"https://oauth2.googleapis.com/token","iat":%s,"exp":%s}' Tffea00\
Ta5d6ff"Te6edf3$CLIENT_EMAILTa5d6ff" Ta5d6ff"Te6edf3$SCOPETa5d6ff" Ta5d6ff"Te6edf3$NOWTa5d6ff" Ta5d6ff"Tff7b72$((Te6edf3NOW Tff7b72+ T79c0ff600Tff7b72))Ta5d6ff" Tb4b4b4| b64urlTff7b72)
Te6edf3SIGTff7b72=Tff7b72$(Tffa657printf Ta5d6ff'%s.%s' Ta5d6ff"Te6edf3$HEADERTa5d6ff" Ta5d6ff"Te6edf3$CLAIMTa5d6ff" Tb4b4b4| openssl dgst -sha256 -sign Ta5d6ff"Te6edf3$TMPTa5d6ff/key.pemTa5d6ff" T79c0ff2>/dev/null Tb4b4b4| b64urlTff7b72) Tffea00\
Tff7b72|| fail_open Ta5d6ff"JWT signing"

Te6edf3TOKENTff7b72=Tff7b72$(curl -sf -X POST https://oauth2.googleapis.com/token Tffea00\
--data-urlencode Ta5d6ff'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer' Tffea00\
--data-urlencode Ta5d6ff"Ta5d6ffassertion=Tffd700${Te6edf3HEADERTffd700}Ta5d6ff.Tffd700${Te6edf3CLAIMTffd700}Ta5d6ff.Tffd700${Te6edf3SIGTffd700}Ta5d6ff" T79c0ff2>/dev/null Tffea00\
Tb4b4b4| python3 -c Ta5d6ff'import json,sys;print(json.load(sys.stdin).get("access_token",""))' T79c0ff2>/dev/nullTff7b72)
Tff7b72[ -n Ta5d6ff"Te6edf3$TOKENTa5d6ff" Tff7b72] Tff7b72|| fail_open Ta5d6ff"token exchange"

Te6edf3APITff7b72=Ta5d6ff"Ta5d6ffhttps://androidpublisher.googleapis.com/androidpublisher/v3/applications/Te6edf3$PKGTa5d6ff"
Te6edf3EDITTff7b72=Tff7b72$(curl -sf -X POST -H Ta5d6ff"Ta5d6ffAuthorization: Bearer Te6edf3$TOKENTa5d6ff" -H Ta5d6ff'Content-Length: 0' Ta5d6ff"Te6edf3$APITa5d6ff/editsTa5d6ff" T79c0ff2>/dev/null Tffea00\
Tb4b4b4| python3 -c Ta5d6ff'import json,sys;print(json.load(sys.stdin).get("id",""))' T79c0ff2>/dev/nullTff7b72)
Tff7b72[ -n Ta5d6ff"Te6edf3$EDITTa5d6ff" Tff7b72] Tff7b72|| fail_open Ta5d6ff"edit insert"

T8b949e# Read-only: GET the track, then discard the edit without committing.
Te6edf3TRACK_JSONTff7b72=Tff7b72$(curl -sf -H Ta5d6ff"Ta5d6ffAuthorization: Bearer Te6edf3$TOKENTa5d6ff" Tffea00\
Ta5d6ff"Te6edf3$APITa5d6ff/edits/Te6edf3$EDITTa5d6ff/tracks/Te6edf3$TRACKTa5d6ff" T79c0ff2>/dev/nullTff7b72)
curl -s -X DELETE -H Ta5d6ff"Ta5d6ffAuthorization: Bearer Te6edf3$TOKENTa5d6ff" Ta5d6ff"Te6edf3$APITa5d6ff/edits/Te6edf3$EDITTa5d6ff" >/dev/null T79c0ff2>Tb4b4b4&T79c0ff1

Tff7b72[ -n Ta5d6ff"Te6edf3$TRACK_JSONTa5d6ff" Tff7b72] Tff7b72|| fail_open Ta5d6ff"track read"

Te6edf3RESULTTff7b72=Tff7b72$(Tffa657printf Ta5d6ff'%s' Ta5d6ff"Te6edf3$TRACK_JSONTa5d6ff" Tb4b4b4| python3 -c Ta5d6ff'
import json, sys
target = int(sys.argv[1])
track = json.load(sys.stdin)
for r in track.get("releases", []):
# Only live release states count — a draft or halted release still needs
# the promotion to run.
if r.get("status") in ("completed", "inProgress") \
and target in [int(c) for c in r.get("versionCodes", [])]:
print("true")
break
else:
print("false")
' Ta5d6ff"Te6edf3$VERSION_CODETa5d6ff" T79c0ff2>/dev/nullTff7b72) Tff7b72|| fail_open Ta5d6ff"track parse"

Tff7b72if Tff7b72[ Ta5d6ff"Te6edf3$RESULTTa5d6ff" Tff7b72= Ta5d6ff"true" Tff7b72]Tb4b4b4; Tff7b72then
Tffa657echo Ta5d6ff"Ta5d6ffversionCode Te6edf3$VERSION_CODETa5d6ff is already live on track 'Te6edf3$TRACKTa5d6ff' — skipping promotion (no new Play submission).Ta5d6ff"
Tff7b72else
Tffa657echo Ta5d6ff"Ta5d6ffversionCode Te6edf3$VERSION_CODETa5d6ff not live on track 'Te6edf3$TRACKTa5d6ff' — promotion will proceed.Ta5d6ff"
Tff7b72fi
emit Ta5d6ff"Te6edf3$RESULTTa5d6ff"

Served by rngit 1.5.0 - Generated in 0.06s